#include "http_conn.h"
#include "../timer/lst_timer.h"
#include "../log/log.h"
#include "../mysql_conn/sql_connection_pool.h"
#include <map>
#include <string>
#include <hiredis/hiredis.h>

#include <cryptopp/sha.h>
#include <cryptopp/hex.h>

extern int m_close_log;

// 定义HTTP响应的一些状态信息
const char *ok_200_title = "OK";
const char *error_400_title = "Bad Request";
const char *error_400_form = "Your request has bad syntax or is inherently impossible to satisfy.\n";
const char *error_403_title = "Forbidden";
const char *error_403_form = "You do not have permission to get file from this server.\n";
const char *error_404_title = "Not Found";
const char *error_404_form = "The requested file was not found on this server.\n";
const char *error_500_title = "Internal Error";
const char *error_500_form = "There was an unusual problem serving the requested file.\n";

// 网站的根目录
const char *doc_root = "./resources";

locker m_lock;
// map<string, string> users;

void http_conn::initmysql_result(connection_pool *connPool)
{
    // 先从连接池中取一个连接
    MYSQL *mysql = NULL;
    connectionRAII mysqlcon(&mysql, connPool);
    // // 在user表中检索username，password数据，浏览器端输入
    // if (mysql_query(mysql, "SELECT username,password FROM user"))
    // {
    //     LOG_ERROR("SELECT error:%s\n", mysql_error(mysql));
    // }
    // // 从表中检索完整的结果集
    // MYSQL_RES *result = mysql_store_result(mysql);
    // // 返回结果集中的列数
    // int num_fields = mysql_num_fields(result);

    // // 返回所有字段结构的数组
    // MYSQL_FIELD *fields = mysql_fetch_fields(result);
    // // 从结果集中获取下一行，将对应的用户名和密码，存入map中
    // while (MYSQL_ROW row = mysql_fetch_row(result))
    // {
    //     string temp1(row[0]);
    //     string temp2(row[1]);
    //     // users[temp1] = temp2;
    // }
    // mysql_free_result(result);
}

std::string calculateSHA256Hash(const std::string &inputData)
{
    CryptoPP::SHA256 sha256;
    byte digest[CryptoPP::SHA256::DIGESTSIZE];
    sha256.CalculateDigest(digest, reinterpret_cast<const byte *>(inputData.c_str()), inputData.length());

    std::string hash;
    CryptoPP::HexEncoder encoder(new CryptoPP::StringSink(hash));
    encoder.Put(digest, sizeof(digest));
    encoder.MessageEnd();

    return hash;
}

int setnonblocking(int fd)
{
    int old_option = fcntl(fd, F_GETFL);
    int new_option = old_option | O_NONBLOCK;
    fcntl(fd, F_SETFL, new_option);
    return old_option;
}

// 向epoll中添加需要监听的文件描述符
void addfd(int epollfd, int fd, bool one_shot)
{
    epoll_event event;
    event.data.fd = fd;
    event.events = EPOLLIN | EPOLLRDHUP;
    if (one_shot)
    {
        // 防止同一个通信被不同的线程处理
        event.events |= EPOLLONESHOT;
    }
    epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event);
    // 设置文件描述符非阻塞
    setnonblocking(fd);
    LOG_INFO("add %d\n", fd);
}

// 从epoll中移除监听的文件描述符
void removefd(int epollfd, int fd)
{
    epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, 0);
    close(fd);
}

// 修改文件描述符，重置socket上的EPOLLONESHOT事件，以确保下一次可读时，EPOLLIN事件能被触发
void modfd(int epollfd, int fd, int ev)
{
    epoll_event event;
    event.data.fd = fd;
    event.events = ev | EPOLLET | EPOLLONESHOT | EPOLLRDHUP;
    epoll_ctl(epollfd, EPOLL_CTL_MOD, fd, &event);
}

// 所有的客户数
int http_conn::m_user_count = 0;
// 所有socket上的事件都被注册到同一个epoll内核事件中，所以设置成静态的
int http_conn::m_epollfd = -1;

// 关闭连接
void http_conn::close_conn()
{
    if (m_sockfd != -1)
    {
        removefd(m_epollfd, m_sockfd);
        m_sockfd = -1;
        m_user_count--; // 关闭一个连接，将客户总数量-1
    }
}

// 初始化连接,外部调用初始化套接字地址
void http_conn::init(int sockfd, const sockaddr_in &addr)
{
    m_sockfd = sockfd;
    m_address = addr;

    // 端口复用
    int reuse = 1;
    setsockopt(m_sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
    addfd(m_epollfd, sockfd, true);
    m_user_count++;
    init();
}

void http_conn::init()
{

    bytes_to_send = 0;
    bytes_have_send = 0;

    m_check_state = CHECK_STATE_REQUESTLINE; // 初始状态为检查请求行
    m_linger = false;                        // 默认不保持链接  Connection : keep-alive保持连接

    m_method = GET; // 默认请求方式为GET
    m_url = 0;
    m_version = 0;
    m_content_length = 0;
    m_host = 0;
    m_start_line = 0;
    m_checked_idx = 0;
    m_read_idx = 0;
    m_write_idx = 0;

    bzero(m_read_buf, READ_BUFFER_SIZE);
    bzero(m_write_buf, READ_BUFFER_SIZE);
    bzero(m_real_file, FILENAME_LEN);

    mysql = NULL;
}

// 循环读取客户数据，直到无数据可读或者对方关闭连接
bool http_conn::read()
{
    if (m_read_idx >= READ_BUFFER_SIZE)
    {
        return false;
    }
    int bytes_read = 0;
    while (true)
    {
        // 从m_read_buf + m_read_idx索引出开始保存数据，大小是READ_BUFFER_SIZE - m_read_idx
        bytes_read = recv(m_sockfd, m_read_buf + m_read_idx,
                          READ_BUFFER_SIZE - m_read_idx, 0);
        if (bytes_read == -1)
        {
            if (errno == EAGAIN || errno == EWOULDBLOCK)
            {
                // 没有数据
                break;
            }
            return false;
        }
        else if (bytes_read == 0)
        { // 对方关闭连接
            return false;
        }
        m_read_idx += bytes_read;
    }
    return true;
}

// 解析一行，判断依据\r\n
http_conn::LINE_STATUS http_conn::parse_line()
{
    char temp;
    for (; m_checked_idx < m_read_idx; ++m_checked_idx)
    {
        temp = m_read_buf[m_checked_idx];
        if (temp == '\r')
        {
            if ((m_checked_idx + 1) == m_read_idx)
            {
                return LINE_OPEN;
            }
            else if (m_read_buf[m_checked_idx + 1] == '\n')
            {
                m_read_buf[m_checked_idx++] = '\0';
                m_read_buf[m_checked_idx++] = '\0';
                return LINE_OK;
            }
            return LINE_BAD;
        }
        else if (temp == '\n')
        {
            if ((m_checked_idx > 1) && (m_read_buf[m_checked_idx - 1] == '\r'))
            {
                m_read_buf[m_checked_idx - 1] = '\0';
                m_read_buf[m_checked_idx++] = '\0';
                return LINE_OK;
            }
            return LINE_BAD;
        }
    }
    return LINE_OPEN;
}

// 解析HTTP请求行，获得请求方法，目标URL,以及HTTP版本号
http_conn::HTTP_CODE http_conn::parse_request_line(char *text)
{
    // GET /index.html HTTP/1.1
    m_url = strpbrk(text, " \t"); // 返回第一个匹配项的指针
    if (!m_url)
    {
        return BAD_REQUEST;
    }
    // GET\0/index.html HTTP/1.1
    *m_url++ = '\0'; // 置位空字符，字符串结束符
    char *method = text;
    if (strcasecmp(method, "GET") == 0)
    { // 忽略大小写比较
        m_method = GET;
    }
    else if (strcasecmp(method, "POST") == 0)
    {
        m_method = POST;
    }
    else
    {
        return BAD_REQUEST;
    }
    // /index.html HTTP/1.1
    // 检索字符串 str1 中第一个不在字符串 str2 中出现的字符下标
    m_version = strpbrk(m_url, " \t");
    if (!m_version)
    {
        return BAD_REQUEST;
    }
    *m_version++ = '\0';
    if (strcasecmp(m_version, "HTTP/1.1") != 0 && strcasecmp(m_version, "HTTP/1.0") != 0)
    {
        return BAD_REQUEST;
    }
    /**
     * http://192.168.0.100:12345/index.html
     */
    // 不分大小写比较前n个字符
    if (strncasecmp(m_url, "http://", 7) == 0)
    {
        m_url += 7;
        // 在参数 str 所指向的字符串中搜索第一次出现字符 c（一个无符号字符）的位置
        m_url = strchr(m_url, '/');
    }
    if (!m_url || m_url[0] != '/')
    {
        return BAD_REQUEST;
    }
    m_check_state = CHECK_STATE_HEADER; // 检查状态变成检查头
    return NO_REQUEST;
}

// 解析HTTP请求的一个头部信息
http_conn::HTTP_CODE http_conn::parse_headers(char *text)
{
    // 遇到空行，表示头部字段解析完毕
    if (text[0] == '\0')
    {
        // 如果HTTP请求有消息体，则还需要读取m_content_length字节的消息体，
        // 状态机转移到CHECK_STATE_CONTENT状态
        if (m_content_length != 0)
        {
            m_check_state = CHECK_STATE_CONTENT;
            return NO_REQUEST;
        }
        // 否则说明我们已经得到了一个完整的HTTP请求
        return GET_REQUEST;
    }
    else if (strncasecmp(text, "Connection:", 11) == 0)
    {
        // 处理Connection 头部字段  Connection: keep-alive
        text += 11;
        text += strspn(text, " \t");
        if (strcasecmp(text, "keep-alive") == 0)
        {
            m_linger = true;
        }
    }
    else if (strncasecmp(text, "Content-Length:", 15) == 0)
    {
        // 处理Content-Length头部字段
        text += 15;
        text += strspn(text, " \t");
        m_content_length = atol(text);
    }
    else if (strncasecmp(text, "Host:", 5) == 0)
    {
        // 处理Host头部字段
        text += 5;
        text += strspn(text, " \t");
        m_host = text;
    }
    else
    {
        LOG_INFO("oop! unknow header %s\n", text);
    }
    return NO_REQUEST;
}

// 我们没有真正解析HTTP请求的消息体，只是判断它是否被完整的读入了
http_conn::HTTP_CODE http_conn::parse_content(char *text)
{
    if (m_read_idx >= (m_content_length + m_checked_idx))
    {
        text[m_content_length] = '\0';
        std::string key;
        std::string value;
        while (*text)
        {
            if (*text == '=' && !key.empty())
            {
                text++;
                value.clear();
                while (*text != '&' && *text != '\0')
                {
                    value += *text;
                    text++;
                }
                if (key == "username")
                {
                    username = value.c_str();
                    // username = calculateSHA256Hash(value).c_str();
                }
                else if (key == "password")
                {
                    password = calculateSHA256Hash(value).c_str();
                }
                key.clear();
            }
            else
            {
                key += *text;
            }
            text++;
        }
        return GET_REQUEST;
    }
    return NO_REQUEST;
}

// 主状态机，解析请求
http_conn::HTTP_CODE http_conn::process_read()
{
    LINE_STATUS line_status = LINE_OK;
    HTTP_CODE ret = NO_REQUEST;
    char *text = 0;
    while (((m_check_state == CHECK_STATE_CONTENT) && (line_status == LINE_OK)) || ((line_status = parse_line()) == LINE_OK))
    {
        // 获取一行数据
        text = get_line();
        m_start_line = m_checked_idx;
        LOG_INFO("got 1 http line: %s\n", text);
        switch (m_check_state)
        {
        case CHECK_STATE_REQUESTLINE:
        {
            ret = parse_request_line(text);
            if (ret == BAD_REQUEST)
            {
                return BAD_REQUEST;
            }
            break;
        }
        case CHECK_STATE_HEADER:
        {
            ret = parse_headers(text);
            if (ret == BAD_REQUEST)
            {
                return BAD_REQUEST;
            }
            else if (ret == GET_REQUEST)
            {
                return do_request();
            }
            break;
        }
        case CHECK_STATE_CONTENT:
        {
            ret = parse_content(text);
            if (ret == GET_REQUEST)
            {
                return do_request();
            }
            line_status = LINE_OPEN;
            break;
        }
        default:
        {
            return INTERNAL_ERROR;
        }
        }
    }
    return NO_REQUEST;
}

bool http_conn::redis_set(string Strusername, string Strpassword)
{
    const char *host = std::getenv("REDIS_HOST");
    if (host == nullptr)
    {
        host = "127.0.0.1";
    }
    redisContext *context = redisConnect(host, 6379); // 连接到本地 Redis 服务器
    string command = "SET " + Strusername + " " + Strpassword + " EX 10";
    redisReply *reply = (redisReply *)redisCommand(context, command.c_str());
    if (reply == nullptr)
    {
        LOG_ERROR("Redis command SET failed");
        // std::cerr << "Redis command SET failed" << std::endl;
        return 1;
    }
    else
    {
        LOG_INFO("SET: %s", reply->str);
        // std::cout << "SET: " << reply->str << std::endl;
        freeReplyObject(reply);
        return 0;
    }
}

bool http_conn::redis_get(string Strusername, string Strpassword)
{
    const char *host = std::getenv("REDIS_HOST");
    if (host == nullptr)
    {
        host = "127.0.0.1";
    }
    redisContext *context = redisConnect(host, 6379); // 连接到本地 Redis 服务器
    string command = "GET " + Strusername;
    redisReply *reply = (redisReply *)redisCommand(context, command.c_str());
    if (reply == nullptr)
    {
        LOG_ERROR("Redis command GET failed");
        // std::cerr << "Redis command GET failed" << std::endl;
        return 0;
    }
    else
    {
        if (reply->type == REDIS_REPLY_NIL)
        {
            LOG_INFO("GET: Data has expired or does not exist");
            // std::cout << "GET: Data has expired or does not exist" << std::endl;
            freeReplyObject(reply);
            return 0;
        }
        else
        {
            LOG_INFO("GET: %s", reply->str);
            // std::cout << "GET: " << reply->str << std::endl;
            freeReplyObject(reply);
            if (reply->str == Strpassword.c_str())
            {
                // std::cout << "return:1 " << std::endl;
                return 1;
            }
            else
            {
                // todo:这里应该是缓存查找到不同，逻辑上应该是直接相信缓存，直接返回
                // 而不是交给mysql再查找一次
                return 0;
            }
        }
    }
}

// 当得到一个完整、正确的HTTP请求时，我们就分析目标文件的属性，
// 如果目标文件存在、对所有用户可读，且不是目录，则使用mmap将其
// 映射到内存地址m_file_address处，并告诉调用者获取文件成功
http_conn::HTTP_CODE http_conn::do_request()
{
    if (m_method == POST)
    {
        std::string Strusername(username);
        std::string Strpassword(password);
        if (strcasecmp(m_url, "/register.cgi") == 0)
        {
            // 注册
            // 如果是注册，先检测数据库中是否有重名的
            // 没有重名的，进行增加数据
            std::string sql_find = "SELECT * FROM user WHERE username = '";
            sql_find += Strusername;
            sql_find += "'";
            if (mysql_query(mysql, sql_find.c_str()) != 0)
            {
                // 查询失败
                LOG_ERROR("SELECT query failed: %s\n", mysql_error(mysql));
                strcpy(m_url, "/registerError.html");
            }
            else
            {
                MYSQL_RES *result = mysql_store_result(mysql);
                if (mysql_num_rows(result) > 0)
                {
                    // 用户名已存在
                    LOG_ERROR("SELECT query failed: %s\n", mysql_error(mysql));
                    strcpy(m_url, "/registerError.html");
                }
                else
                {
                    // TODO:这里的username和password似乎取不到正常的值了，而且也必须mysql_free_result一次
                    std::string sql_insert = "INSERT INTO user(username, password) VALUES('";
                    sql_insert += Strusername;
                    sql_insert += "', '";
                    sql_insert += Strpassword;
                    sql_insert += "')";

                    // if (users.find(Strusername) == users.end())
                    // {
                    m_lock.lock();
                    int res = mysql_query(mysql, sql_insert.c_str());
                    // cout << "mysql_error(mysql)=" << mysql_error(mysql) << endl;
                    if (!res)
                    {
                        // users.insert(pair<string, string>(Strusername, Strpassword));
                        strcpy(m_url, "/log.html");
                        redis_set(Strusername, Strpassword);
                    }
                    else
                    {
                        strcpy(m_url, "/registerError.html");
                    }
                    m_lock.unlock();
                }
                mysql_free_result(result);
            }
        }
        else if (strcasecmp(m_url, "/login.cgi") == 0)
        {
            // 登录
            if (redis_get(Strusername, Strpassword) == 1)
            {
                strcpy(m_url, "/welcome.html");
            }
            else
            {
                std::string sql_match = "SELECT * FROM user WHERE username = '";
                sql_match += Strusername;
                sql_match += "' and password='";
                sql_match += Strpassword;
                sql_match += "'";
                if (mysql_query(mysql, sql_match.c_str()))
                {
                    strcpy(m_url, "/logError.html");
                }
                else
                {
                    MYSQL_RES *res = mysql_store_result(mysql);
                    if (res)
                    {
                        MYSQL_ROW row;
                        int cnt = 0;
                        while ((row = mysql_fetch_row(res)))
                        {
                            cnt++;
                        }
                        if (cnt == 1)
                        {
                            redis_set(Strusername, Strpassword);
                            strcpy(m_url, "/welcome.html");
                        }
                        else
                        {
                            strcpy(m_url, "/logError.html");
                        }
                        // 如果有匹配的记录，处理结果
                        // 释放结果集
                        mysql_free_result(res);
                    }
                    else
                    {
                        strcpy(m_url, "/logError.html");
                    }
                }
            }
        }
    }
    //  "./resources"
    strcpy(m_real_file, doc_root);
    int len = strlen(doc_root);
    strncpy(m_real_file + len, m_url, FILENAME_LEN - len - 1);
    // 获取m_real_file文件的相关的状态信息，-1失败，0成功
    if (stat(m_real_file, &m_file_stat) < 0)
    {
        return NO_RESOURCE;
    }

    // 判断访问权限
    if (!(m_file_stat.st_mode & S_IROTH))
    {
        return FORBIDDEN_REQUEST;
    }

    // 判断是否是目录
    if (S_ISDIR(m_file_stat.st_mode))
    {
        return BAD_REQUEST;
    }

    // 以只读方式打开文件
    int fd = open(m_real_file, O_RDONLY);
    // 创建内存映射
    m_file_address = (char *)mmap(0, m_file_stat.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
    close(fd);
    return FILE_REQUEST;
}

// 对内存映射区执行munmap操作
void http_conn::unmap()
{
    if (m_file_address)
    {
        munmap(m_file_address, m_file_stat.st_size);
        m_file_address = 0;
    }
}

// 写HTTP响应
bool http_conn::write()
{
    int temp = 0;

    if (bytes_to_send == 0)
    {
        // 将要发送的字节为0，这一次响应结束。
        modfd(m_epollfd, m_sockfd, EPOLLIN);
        init();
        return true;
    }

    while (1)
    {
        // 分散写
        temp = writev(m_sockfd, m_iv, m_iv_count);
        if (temp <= -1)
        {
            // 如果TCP写缓冲没有空间，则等待下一轮EPOLLOUT事件，虽然在此期间，
            // 服务器无法立即接收到同一客户的下一个请求，但可以保证连接的完整性。
            if (errno == EAGAIN)
            {
                modfd(m_epollfd, m_sockfd, EPOLLOUT);
                return true;
            }
            unmap();
            return false;
        }

        bytes_have_send += temp;
        bytes_to_send -= temp;

        if (bytes_have_send >= m_iv[0].iov_len)
        {
            m_iv[0].iov_len = 0;
            m_iv[1].iov_base = m_file_address + (bytes_have_send - m_write_idx);
            m_iv[1].iov_len = bytes_to_send;
        }
        else
        {
            m_iv[0].iov_base = m_write_buf + bytes_have_send;
            m_iv[0].iov_len = m_iv[0].iov_len - temp;
        }

        if (bytes_to_send <= 0)
        {
            // 没有数据要发送了
            unmap();
            modfd(m_epollfd, m_sockfd, EPOLLIN);

            if (m_linger)
            {
                init();
                return true;
            }
            else
            {
                return false;
            }
        }
    }
}

// 往写缓冲中写入待发送的数据
bool http_conn::add_response(const char *format, ...)
{
    if (m_write_idx >= WRITE_BUFFER_SIZE)
    {
        return false;
    }
    va_list arg_list;
    va_start(arg_list, format);
    int len = vsnprintf(m_write_buf + m_write_idx, WRITE_BUFFER_SIZE - 1 - m_write_idx, format, arg_list);
    if (len >= (WRITE_BUFFER_SIZE - 1 - m_write_idx))
    {
        return false;
    }
    m_write_idx += len;
    va_end(arg_list);
    return true;
}

bool http_conn::add_status_line(int status, const char *title)
{
    return add_response("%s %d %s\r\n", "HTTP/1.1", status, title);
}

bool http_conn::add_headers(int content_len)
{
    add_content_length(content_len);
    add_content_type();
    add_linger();
    add_blank_line();
    return 0;
}

bool http_conn::add_content_length(int content_len)
{
    return add_response("Content-Length: %d\r\n", content_len);
}

bool http_conn::add_linger()
{
    return add_response("Connection: %s\r\n", (m_linger == true) ? "keep-alive" : "close");
}

bool http_conn::add_blank_line()
{
    return add_response("%s", "\r\n");
}

bool http_conn::add_content(const char *content)
{
    return add_response("%s", content);
}

bool http_conn::add_content_type()
{
    // css样式要有特殊的Content-Type字段
    if (strstr(m_url, ".css") != NULL)
    { // 忽略大小写比较
        return add_response("Content-Type:%s\r\n", "text/css");
    }
    else if (strstr(m_url, ".jpg") != NULL)
    {
        return add_response("Content-Type:%s\r\n", "image/jpeg");
    }
    return add_response("Content-Type:%s\r\n", "text/html");
}

// 根据服务器处理HTTP请求的结果，决定返回给客户端的内容
bool http_conn::process_write(HTTP_CODE ret)
{
    switch (ret)
    {
    case INTERNAL_ERROR:
        add_status_line(500, error_500_title);
        add_headers(strlen(error_500_form));
        if (!add_content(error_500_form))
        {
            return false;
        }
        break;
    case BAD_REQUEST:
        add_status_line(400, error_400_title);
        add_headers(strlen(error_400_form));
        if (!add_content(error_400_form))
        {
            return false;
        }
        break;
    case NO_RESOURCE:
        add_status_line(404, error_404_title);
        add_headers(strlen(error_404_form));
        if (!add_content(error_404_form))
        {
            return false;
        }
        break;
    case FORBIDDEN_REQUEST:
        add_status_line(403, error_403_title);
        add_headers(strlen(error_403_form));
        if (!add_content(error_403_form))
        {
            return false;
        }
        break;
    case FILE_REQUEST:
        add_status_line(200, ok_200_title);
        add_headers(m_file_stat.st_size);
        m_iv[0].iov_base = m_write_buf;
        m_iv[0].iov_len = m_write_idx;
        m_iv[1].iov_base = m_file_address;
        m_iv[1].iov_len = m_file_stat.st_size;
        m_iv_count = 2;

        bytes_to_send = m_write_idx + m_file_stat.st_size;

        return true;
    default:
        return false;
    }

    m_iv[0].iov_base = m_write_buf;
    m_iv[0].iov_len = m_write_idx;
    m_iv_count = 1;
    bytes_to_send = m_write_idx;
    return true;
}

// 由线程池中的工作线程调用，这是处理HTTP请求的入口函数
void http_conn::process()
{
    // 解析HTTP请求
    HTTP_CODE read_ret = process_read();
    if (read_ret == NO_REQUEST)
    {
        modfd(m_epollfd, m_sockfd, EPOLLIN);
        return;
    }
    // 生成响应
    bool write_ret = process_write(read_ret);
    if (!write_ret)
    {
        close_conn();
    }
    modfd(m_epollfd, m_sockfd, EPOLLOUT);
}